from dataclasses import dataclass, field
import logging
from {{packageName}}.request_handler import AbstractRequestHandler
from {{packageName}}.content_handler import ContentHandler, JSONContentHandler

LOGGER = logging.getLogger(__name__)


@dataclass
class DataEntity:
    href: 'str' = field(default=None, metadata={'attr_map': 'href'})

    @classmethod
    def model_mapping(cls):
        for field in cls.__dataclass_fields__.values():
            yield field.name, field.type, field.metadata['attr_map']

    @classmethod
    def class_name(cls) -> str:
        return cls.__name__

    @classmethod
    def model_name(cls) -> str:
        pass

    def _reload_from_dict(self, dictionary: dict):
        for field_name, field_type, model_name in self.model_mapping():
            if model_name in dictionary.keys():
                sub_cls = object
                if 'models.' in field_type:
                    sub_cls_str = field_type.replace('models.', '').replace('List[', '').replace(']', '')
                    mod = __import__('{{modelPackage}}', fromlist=[sub_cls_str])
                    sub_cls = getattr(mod, sub_cls_str)

                raw_value = dictionary[model_name]

                if issubclass(sub_cls, DataEntity):
                    try:
                        if isinstance(raw_value, dict):
                            self.__setattr__(field_name, sub_cls.from_dict(raw_value))
                        elif isinstance(raw_value, list):
                            values = []
                            for item in raw_value:
                                values.append(sub_cls.from_dict(item))
                            self.__setattr__(field_name, values)
                    except TypeError:
                        raise TypeError(
                            f'Failed to instantiate sub-entity {field_name} of type {sub_cls.class_name()} for {type(self)}.')
                else:
                    self.__setattr__(field_name, raw_value)
        LOGGER.debug(f'(Re)loaded {self.class_name()} : {self}.')

    @classmethod
    def from_dict(cls, dictionary: dict):
        LOGGER.debug(f'Instantiating {cls.class_name()} from {dictionary}.')
        entity = cls()
        entity._reload_from_dict(dictionary)
        return entity

    def to_dict(self) -> dict:
        LOGGER.debug(f'Serializing {type(self).class_name()} instance as dict.')

        result = {}
        for field_name, field_type, model_name in self.model_mapping():
            attribute = getattr(self, field_name)
            if attribute:
                if isinstance(attribute, DataEntity):
                    result[model_name] = attribute.to_dict()
                elif isinstance(attribute, list):
                    values = []
                    for value in attribute:
                        values.append(value.to_dict())
                    result[model_name] = values
                else:
                    result[model_name] = attribute
        return result

    def reload_from_api(self, handler: AbstractRequestHandler, content_handler: ContentHandler = JSONContentHandler()):
        if not self.href:
            raise AttributeError(f'{self.class_name()} either does not have href property populated or is not set.')
        response = handler.request(self.href, 'GET')
        response_dict = content_handler.deserialize(response)
        self._reload_from_dict(response_dict)

    def __repr__(self):
        return self.to_dict()
